package com.romotc.qqrobot.webqq.qqInterfaceCall;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.commons.lang.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;

import com.romotc.IpTools;
import com.romotc.qqrobot.bean.QQData;
import com.romotc.qqrobot.bean.QQDataService;
import com.romotc.qqrobot.bean.QQLogedin;
import com.romotc.qqrobot.webqq.SysConstant;
import com.romotc.qqrobot.webqq.WebQQConstant;
import com.romotc.qqrobot.webqq.qqInterfaceCall.dataProcessor.DateProcessor;
import com.romotc.qqrobot.webqq.qqInterfaceCall.dataProcessor.JsonDataProcessor;
import com.romotc.qqrobot.webqq.qqInterfaceCall.dataProcessor.SessioinProcessor;
import com.romotc.scache.RequestSpy;
import com.romotc.scache.httpreq;

public abstract class QQInterfaceCall {

	public static final String Key_ResBody = "QQInterfaceCall_Request_Res_Body";
	/**
	 * Call的结果：1成功，0失败 不继续调用
	 */
	public static final String Key_CallResult = "QQInterfaceCall_Result";
	/**
	 * 结果说明
	 */
	public static final String Key_CallResult_Resion = "QQInterfaceCall_Result_Resion";
	/**
	 * Call结果常量
	 * 成功：1
	 */
	public static final int Key_CallResult_Success = 1;
	/**
	 * Call结果常量
	 * 失败：1
	 */
	public static final int Key_CallResult_Error = 0;
	
	public static final int httpv = 11;
	private static Random random = new Random();
	
	
	public JSONObject call(QQLogedin curQqLogedIn, JSONObject reqDataObj,
			DateProcessor processor) throws Exception{
		return call(curQqLogedIn, reqDataObj, processor,1);
	}
	
	public JSONObject call(QQLogedin curQqLogedIn, JSONObject reqDataObj,
			DateProcessor processor[]) throws Exception{
		return call(curQqLogedIn, reqDataObj, processor,1);
	}
	
	/**
	 * 发送消息的编码格式
	 */
	public static final String reqCharset = "utf-8"; 
	
	
	public JSONObject call(QQLogedin loginQQ, JSONObject reqDataObj,
			DateProcessor processor, int second) throws Exception{
		DateProcessor[] processorArray = processor == null ? null : new DateProcessor[]{processor};
		return call(loginQQ, reqDataObj, processorArray, second);
	}
	/**
	 * 调用腾讯接口 之后将结果交给processor处理
	 * @param call
	 * @param processor
	 * @param JSONObject reqDataObj Post的请求体 || 或Get的请求参数
	 * @return 从本次调用返回信息中解析好的数据
	 */
	public JSONObject call(QQLogedin loginQQ, JSONObject reqDataObj,
				DateProcessor[] processor, int second) throws Exception{
		//1.前置call
		if(hasPreCall(loginQQ, reqDataObj)) {
			reqDataObj = preCall(loginQQ, reqDataObj);
			
			//检查PreCall是否成功，失败则返回不继续进行
			int preCallResult = reqDataObj.optInt(Key_CallResult, Key_CallResult_Success);
			if(Key_CallResult_Error == preCallResult) {
				System.out.println(this + ">>前置Call调用返回失败，不再继续调用. " + reqDataObj.optString(Key_CallResult_Resion));
				return reqDataObj;
			}
			
			//连续的请求之间间隔2秒 + 0-5秒的时间， 防止被腾讯服务器封杀
//			int sleepTime = random.nextInt(3) * 1000;
//			System.err.println("preCall finish,sleep:" + sleepTime / 1000 + "s");
//			Thread.sleep(sleepTime);
//			System.err.println("preCall finish,sleeped----");
		}
		
		
		reqDataObj = curCall(loginQQ, reqDataObj, processor);			

		//DBManager.closeConnection();
		//检查call是否成功，失败则返回不继续进行
		int preCallResult = reqDataObj.optInt(Key_CallResult, Key_CallResult_Success);
		if(Key_CallResult_Error == preCallResult) {
			System.out.println(this + ">>call调用返回失败，不再继续调用. " + reqDataObj.optString(Key_CallResult_Resion));
			return reqDataObj;
		}
		
		Thread.sleep(second * 1000);
		
		//3.后置Call
		if(hasPostCall(loginQQ, reqDataObj)) {
			reqDataObj = postCall(loginQQ, reqDataObj);
		}
		
		return reqDataObj;
	}
	
	
	public JSONObject preCall(QQLogedin loginQQ, JSONObject reqDataObj) throws Exception {
		return reqDataObj;
	}
	
	
	/**
	 * 有参数版的preCall判断
	 * @param loginQQ
	 * @param reqDataObj
	 * @return
	 */
	public boolean hasPreCall(QQLogedin loginQQ, JSONObject reqDataObj) {
		return false;
	}

	public boolean hasPostCall(QQLogedin loginQQ, JSONObject reqDataObj) {
		return false;
	}
	
	public JSONObject postCall(QQLogedin loginQQ, JSONObject reqDataObj) throws Exception {
		return reqDataObj;
	}

	
	/**
	 * 当前Call发起调用
	 * @param loginQQ
	 * @param reqDataObj
	 * @param processor
	 * @throws Exception
	 */
	private JSONObject curCall(QQLogedin loginQQ, JSONObject reqDataObj,
			DateProcessor[] processor) throws Exception{
		//检查是否不符合条件
		String url = getReqUrl(reqDataObj, loginQQ);
		if(StringUtils.isBlank(url)) {
			return reqDataObj;
		}
		
		//请求体
		String reqBody = buildReqBody(reqDataObj, loginQQ);//msgBody.toString();
		if(reqBody == null)
			reqBody = "";
		
		//输入输出流
		byte[] reqBodyArray = reqBody.getBytes(reqCharset);
		DataInputStream in = new DataInputStream(new ByteArrayInputStream(reqBodyArray));
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		DataOutputStream ou = new DataOutputStream(bos);
		int reqBodyLen = reqBodyArray.length;
		
		//先取数据库请求头模板
		ArrayList<String> headers = myReqHeaders(reqBodyLen, loginQQ, reqDataObj);
		if(headers == null)//数据库中没有配模板则使用代码中的
			headers = reqHeadersTemplet(reqBodyLen, loginQQ, getReqUrl(reqDataObj, loginQQ));

		//构建request对象
		RequestSpy rq = new RequestSpy(httpv, getReq_method(), url , headers, in, ou, getLogLevel());
		boolean finished = false;
		
		//循环调用 直到请求成功 for多线程下更换IP时出现其他请求线程断网
		int sleep = 1000;
		while(!finished){
			try {
				String reqBodyString = ((RequestSpy)rq).getReqBody();
				String reqCookie = ((RequestSpy)rq).getReqCookiesStr();
				String reqInfos = ((RequestSpy)rq).getReqInfos();
				//交友manager处理
				IpTools.reqNet();
				counterRequest(1);
				httpreq.manager.process_request(rq);
				//2.发起当前Call
				finished = true;
				
			} catch (Throwable e) {/* break keepalive */
				System.out.println("Error @ QQInterfaceCall.call" + e.getMessage());
				rq = new RequestSpy(httpv, getReq_method(), url , headers, in, ou, getLogLevel());
				
				if(sleep > 6000) {
					finished = true;
				}
				sleep += 1000;
				System.out.println("请求出现异常，" + sleep/1000 + "s后重新发起调用：" + url);
			}finally {
				IpTools.finishUseNet();
				counterRequest(-1);
			}
			
			Thread.sleep(sleep);
		}
		
		String resBody = rq.getResBody();
		
		//公共结果处理：将返回的Session信息保存到QQlogin中
		SessioinProcessor.getInstance().processResponse(loginQQ, rq, null);
		
		//请求结果封装为Bean
		if(getResClassType() != null) {
			reqDataObj = JsonDataProcessor.getInstance(getResClassType(), getResObjKey())
				.processResponse(loginQQ, rq, reqDataObj);
			
			//检查是否调用成功
			if(!checkIsCallReturnSuccess(reqDataObj, loginQQ)) {
				//如果是失败直接放置错误标志
				reqDataObj.put(Key_CallResult, Key_CallResult_Error);
			}
		}
		
		
		//交给结果处理程序处理
		if(processor != null || processor.length > 0) {
			for (int i = 0; i < processor.length; i++) {
				if(processor[i] == null)
					continue;
				reqDataObj =  processor[i].processResponse(loginQQ, rq, reqDataObj);
			}
		}
		else {
			reqDataObj.put(Key_ResBody, rq.getResBody());
		}
		
		return reqDataObj;
	}
	
	private static Integer counter = 0;
	private static void counterRequest(int i) {
		synchronized (counter) {
			counter += i;
		}
	}
	
	public static int getCounter() {
		synchronized (counter) {
			return counter;
		}
	}

	/**
	 * 请求头
	 * @param reqBodyLen
	 * @param loginQQ
	 * @return
	 */
	public abstract ArrayList<String> myReqHeaders(int reqBodyLen, QQLogedin loginQQ, JSONObject reqDataObj) ;
	
	/**
	 * 从数据库中取请求头模板
	 */
	ArrayList<String> reqHeadersTemplet(int reqBodyLen, QQLogedin loginQQ, String reqUrl) {
		

		if(StringUtils.isNotBlank(reqUrl))
		{
			//根据url取最新版本的Header模板
			QQData tempData = QQDataService.qryLatestQQDataTemplet(reqUrl);
			if(tempData != null) {
				String reqHeadersStr = tempData.getReqHeadersStr();
				String[] headers = reqHeadersStr.split(", ");
				String cont_type = null;
				if(headers != null && headers.length > 0) {
					ArrayList<String> retList = new ArrayList<String>(headers.length);
					
					for(String header : headers) {
						if(header.startsWith("Cookie:")) {
							String cookie = loginQQ.getCookieString();
							retList.add("Cookie: " + loginQQ.getCookieString());
						}else if(header.startsWith("Content-Length:")) {
							if(reqBodyLen > 0)
								retList.add("Content-Length: " + reqBodyLen);
						}else if(header.startsWith("Host:")) {
							retList.add(header);
							retList.add("Proxy-Connection: keep-alive");
						}else if(header.startsWith("Content-Type:")){
							cont_type = header;
						}
						else {
							retList.add(header);
						}
					}
					
					if(cont_type != null) 
						retList.add(cont_type);//放最后
					return retList;
				}
			}
		}
	
		return null;
	}
	
	/**
	 * 消息体
	 * */
	public abstract String buildReqBody(JSONObject reqDataObj, QQLogedin loginQQ) throws Exception;
	
	
	
	public abstract int getLogLevel();

	public abstract int getReq_method();

	/**
	 * 请求的url 返回空的时候不发起调用
	 * */
	public abstract String getReqUrl(JSONObject reqDataObj, QQLogedin loginQQ) throws Exception;
	
	/**
	 * 接口返回数据解析Bean
	 */
	public Class getResClassType()
	{
		return com.romotc.qqrobot.webqq.bean.BaseCallResData.class;
	}
	
	/**
	 * 接口数据解析成Bean之后放置在reqDataObj中的键值
	 */
	public String getResObjKey() {
		return getClass().getSimpleName() + "_ResObj";
	}
	
	/**
	 * 检查接口返回的数据是否为成功
	 * @throws JSONException 
	 */
	public boolean checkIsCallReturnSuccess(JSONObject reqDataObj, QQLogedin loginQQ) throws JSONException {
		Object o = reqDataObj.opt(getResObjKey());
		//默认的
		if(o instanceof com.romotc.qqrobot.webqq.bean.BaseCallResData) {
			com.romotc.qqrobot.webqq.bean.BaseCallResData resObj = (com.romotc.qqrobot.webqq.bean.BaseCallResData)reqDataObj.opt(getResObjKey());
			if(resObj.getResult() == WebQQConstant.Call_ResFlag_DefaultSuccessFlag) {
				reqDataObj.put(getResResultKey(), true);
				return true;
			}else {
				return false;
			}
			
		}
		//默认返回True
		return true;
	}
	
	/**
	 * 接口调用是否成功的标记放置在ReqDataObj中的键值
	 */
	public String getResResultKey() {
		return getClass().getSimpleName() + "_CallResult";
	}
	
	public static ArrayList<String> buildReqHeaders(int reqBodyLen, QQLogedin loginQQ,
			String referer, String host, String origin)
	{
		return buildReqHeaders(reqBodyLen, loginQQ, referer, host, origin,null,null,null,null,null,null);
	}
	
	public static ArrayList<String> buildReqHeaders(QQLogedin loginQQ,
			String referer, String host)
	{
		return buildReqHeaders(-9, loginQQ, referer, host,null,null,null,null,null,null,null);
	}
	
	public static  ArrayList<String> buildReqHeaders(int reqBodyLen, QQLogedin loginQQ
			,String referer, String host, String origin, String content_type,
			String accept, String accept_encoding, String accept_language,
			String accept_charset, String user_agent) {
		ArrayList<String> headers = new ArrayList<String>();
		if(StringUtils.isNotBlank(host))
			headers.add("Host: " + host);
		
		headers.add("Proxy-Connection: keep-alive");
		
		if(reqBodyLen > 0)
			headers.add("Content-Length: " + reqBodyLen);
		
		if(StringUtils.isNotBlank(origin))
			headers.add("Origin: " + origin);
		
		if(StringUtils.isNotBlank(user_agent))
			headers.add("User-Agent: " + user_agent);
		else
			headers.add("User-Agent: " + SysConstant.User_Agent);
		
		if(StringUtils.isNotBlank(content_type))
			headers.add("Content-Type: " + content_type);
		
		if(StringUtils.isNotBlank(accept))
			headers.add("Accept: " + accept);
		else
			headers.add("Accept: */*");
		
		if(StringUtils.isNotBlank(referer))
			headers.add("Referer: " + referer);
		
		if(StringUtils.isNotBlank(accept_encoding))
			headers.add("Accept-Encoding: " + accept_encoding);
		else
			headers.add("Accept-Encoding: gzip,deflate,sdch");
		
		if(StringUtils.isNotBlank(accept_language))
			headers.add("Accept-Language: " + accept_language);
		else
			headers.add("Accept-Language: zh-CN,zh;q=0.8");
		
		if(StringUtils.isNotBlank(accept_charset))
			headers.add("Accept-Charset: " + accept_charset);
		else 
			headers.add("Accept-Charset: UTF-8,*;q=0.5");
		
		headers.add("Cookie: " + loginQQ.getCookieString());
		return headers;
	}
	
	
	public static ArrayList<String> replaceReqHeaders(ArrayList<String> s, String string,
			String string2) {
		if(s == null || !s.isEmpty()) {
			s = new ArrayList<String>();
			s.add(string + ": " + string2);
			return s;
		}
		
		int i = 0;
		for(String header : s) {
			if(header.startsWith(string)) {
				s.set(i, string + ": " + string2);
				break;
			}
		}
		
		return s;
		
	}


}
